package com.hup.sidebar.view.component;

import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;
import android.widget.TextView;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Objects;

import lombok.Setter;
import lombok.ToString;


/**
 * 撤销重做工具类
 * .不支持键盘按键
 * .稍微有点用的资料 https://blog.csdn.net/weixin_63088467/article/details/129001853
 *
 * @author hugan
 * @date 2023/5/4
 */
@SuppressWarnings("ConstantConditions")
public class UndoManager implements TextWatcher {
    private static final String TAG = UndoManager.class.getSimpleName();

    private final TextView textView;
    private final LinkedList<EditRecord> undoList;
    private final LinkedList<EditRecord> redoList;
    private CharSequence beforeChangedText;
    private boolean undoRedoing = false;
    @Setter
    private int maxHistory = 20;

    private UndoManager(TextView textView) {
        this.textView = textView;
        undoList = new LinkedList<>();
        redoList = new LinkedList<>();
    }

    /**
     * 实际是"替换文本"前的回调
     *
     * @param s 变化前的文本
     * @param start 把s从startIndex开始"删除内容"(包含的)
     * @param count "删除内容"的长度, 等于0时,就是[纯新增内容]
     * @param after 从startIndex开始插入的文本长度(包含的), 等于0时,就是[纯删除]
     */
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if (undoRedoing) return;
        //Log.i(TAG, "beforeTextChanged: " + start + ", " + count + ", " + after + ", s=" + s);
        beforeChangedText = new StringBuilder(s);
    }

    /**
     * 与beforeTextChanged的区别只在"currentText"不同!!!
     *
     * @param currentText 变化后的文本
     * @param start 把变化前的文本,从第start开始,
     * @param deleteCount 从第start开始,删除before个字符,
     * @param insertCount 从start开始(包含), 新增count个字符
     */
    @Override
    public void onTextChanged(CharSequence currentText, int start, int deleteCount, int insertCount) {
        if (undoRedoing) return;
        //Log.i(TAG, "onTextChanged: " + start + ", " + deleteCount + ", " + insertCount + ", currentText=" + currentText);
        if (beforeChangedText == null) {
            Log.e(TAG, "onTextChanged: beforeChangedText==null????");//未知情况
            return;
        }

        redoList.clear();
        if (deleteCount > 0 && insertCount == 0) {
            //纯删除
            undoList.add(EditRecord.delete(start, beforeChangedText.subSequence(start, start + deleteCount)));
        } else if (deleteCount == 0 && insertCount > 0) {
            //纯新增
            undoList.add(EditRecord.add(start, currentText.subSequence(start, start + insertCount)));
        } else {
            //替换
            undoList.add(EditRecord.replace(start,
                    currentText.subSequence(start, start + insertCount),
                    beforeChangedText.subSequence(start, start + deleteCount)));
        }
        if (undoList.size() > maxHistory) undoList.pollFirst();
        beforeChangedText = null;
        //Log.i(TAG, "onTextChanged: " + undoList);
    }

    @Override
    public void afterTextChanged(Editable s) {
    }

    //========
    public void reset() {
        if (undoRedoing) return;
        undoList.clear();
        redoList.clear();
    }

    public void undo() {
        if (undoRedoing || undoList.isEmpty()) return;
        //Log.i(TAG, "undo: " + undoList);
        Objects.requireNonNull(undoList.pollLast()).undo(this);
    }

    public void redo() {
        if (undoRedoing || redoList.isEmpty()) return;
        //Log.i(TAG, "redo: " + redoList);
        Objects.requireNonNull(redoList.pollLast()).redo(this);
    }

    /**
     * 给组件设置UndoManager;
     * - 会处理重复添加的情况,但不建议重复调用!
     */
    public static UndoManager setUndoManager(TextView textView) {
        assert textView != null;
        try {
            UndoManager undoManager = getUndoManager(textView);
            if (undoManager != null) {
                Log.w(TAG, "setUndoManager: 已存在UndoManager");
                return undoManager;
            }
        } catch (Exception e) {
            /*
            不会有异常??
            异常+重复调用时,会添加多个UndoManager,但对功能没影响,影响性能
             */
            Log.e(TAG, "setUndoManager: " + e.getMessage(), e);
        }
        UndoManager undoManager = new UndoManager(textView);
        textView.addTextChangedListener(undoManager);
        return undoManager;
    }

    @SuppressWarnings({"JavaReflectionMemberAccess", "unchecked", "DiscouragedPrivateApi"})
    public static UndoManager getUndoManager(TextView textView) throws Exception {
        Field field = TextView.class.getDeclaredField("mListeners");
        field.setAccessible(true);
        ArrayList<TextWatcher> list = (ArrayList<TextWatcher>) field.get(textView);
        if (list == null) return null;
        for (TextWatcher textWatcher : list) {
            if (textWatcher instanceof UndoManager) return (UndoManager) textWatcher;
        }
        return null;
    }


    @ToString
    private static class EditRecord {
        private static final int DELETE = 0;
        private static final int ADD = 1;
        private static final int REPLACE = 2;

        private int type;
        private int start;
        private int insertCount;
        private CharSequence deleteText;
        private CharSequence insertText;

        public void undo(UndoManager manager) {
            manager.undoRedoing = true;
            TextView textView = manager.textView;
            CharSequence current = textView.getText();
            StringBuilder sb = new StringBuilder(current);
            int selectIndex = 0;
            switch (type) {
                case DELETE:
                    sb.insert(start, deleteText);
                    selectIndex = start + deleteText.length();
                    break;
                case ADD:
                    sb.delete(start, start + insertText.length());
                    selectIndex = start;
                    break;
                case REPLACE:
                    sb.delete(start, start + insertCount);
                    sb.insert(start, deleteText);
                    selectIndex = start + deleteText.length();
                    break;
            }
            textView.setText(sb);
            if (textView instanceof EditText) ((EditText) textView).setSelection(selectIndex);

            manager.redoList.add(this);
            manager.undoRedoing = false;
        }

        public void redo(UndoManager manager) {
            manager.undoRedoing = true;
            TextView textView = manager.textView;
            CharSequence current = textView.getText();
            StringBuilder sb = new StringBuilder(current);
            int selectIndex = 0;
            switch (type) {
                case DELETE:
                    sb.delete(start, start + deleteText.length());
                    selectIndex = start;
                    break;
                case ADD:
                    sb.insert(start, insertText);
                    selectIndex = start + insertText.length();
                    break;
                case REPLACE:
                    sb.delete(start, start + deleteText.length());
                    sb.insert(start, insertText);
                    selectIndex = start + insertText.length();
                    break;
            }
            textView.setText(sb);
            if (textView instanceof EditText) ((EditText) textView).setSelection(selectIndex);

            manager.undoList.add(this);
            manager.undoRedoing = false;
        }

        public static EditRecord delete(int start, CharSequence deleteText) {
            EditRecord res = new EditRecord();
            res.type = DELETE;
            res.start = start;
            res.deleteText = deleteText;
            return res;
        }

        public static EditRecord add(int start, CharSequence insertText) {
            EditRecord res = new EditRecord();
            res.type = ADD;
            res.start = start;
            res.insertText = insertText;
            return res;
        }

        public static EditRecord replace(int start, CharSequence insertText, CharSequence deleteText) {
            EditRecord res = new EditRecord();
            res.type = REPLACE;
            res.start = start;
            res.insertCount = insertText.length();
            res.insertText = insertText;
            res.deleteText = deleteText;
            return res;
        }
    }

}
